﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using log4net;

namespace HttpTunnel
{
    class RequestProcesser
    {
        private static readonly ILog Logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private const string UserAgentKey = "User-Agent:";
        private const string HostKey = "Host:";
        private const string UserAgentStartKey = "iSafe";
        private const string HTTPMethodGetKey = "GET";
        private const string HTTPMethodPostKey = "POST";
        private const string HTTPMethodConnectKey = "CONNECT";

        private const string KeepAliveHeaderKey = "Proxy-Connection: keep-alive";


        public static int MaxRequestLength { get; private set; }

        public static IEnumerable<string> Keywords { get; private set; }

        public static string DefaultUserAgent { get; private set; }
        public static Version MinBrowserVersion { get; private set; }

        public static bool IsFilterOn { get; private set; }

        public static IEnumerable<string> WhiteList { get; private set; }
        public static IEnumerable<string> InternalList { get; private set; }
        public static IEnumerable<string> BlackList { get; private set; }

        public static void Initialize(Config config)
        {
            IsFilterOn = config.IsFilterOn;

            MaxRequestLength = config.MaxRequestLength;
            WhiteList = config.WhiteList.Select(item => item.Replace("*", "")).ToArray();
            BlackList = config.BlackList.Select(item => item.Replace("*", "")).ToArray();
            InternalList = config.InternalList.Select(item => item.Replace("*", "")).ToArray();
            Keywords = config.Keywords;

            DefaultUserAgent = config.UserAgent;
            MinBrowserVersion = config.MinBrowserVersion;
        }

        public static bool IsInternalRequest(string request, out UrlValidateResult urlValidateResult, out IPEndPoint remoteEndPoint, out bool isKeepAlive)
        {
            remoteEndPoint = null;
            isKeepAlive = false;
            urlValidateResult = UrlValidateResult.Normal;
            try
            {
                //Parse the Http Request Type
                var firstSpaceIndex = request.IndexOf(' ');
                var requestType = request.Substring(0, firstSpaceIndex).ToUpper();

                //Parse the Requested Path
                var sencondSpaceIndex = request.IndexOf(' ', firstSpaceIndex + 1);
                var requestUrl = request.Substring(firstSpaceIndex + 1, sencondSpaceIndex - firstSpaceIndex - 1);

                var isHttps = HTTPMethodConnectKey.Equals(requestType, StringComparison.OrdinalIgnoreCase);
                if (isHttps)
                {
                    return false; //not support internal https sites... iSafe browser is only used to visit internet.
                }

                var host = new Uri(requestUrl).Host;

                if (InternalList.Any(item => host.IndexOf(item, StringComparison.OrdinalIgnoreCase) > -1))
                {
                    var address = Dns.GetHostEntry(host).AddressList.First(item=>item.AddressFamily == AddressFamily.InterNetwork);
                    remoteEndPoint = new IPEndPoint(address, 80);
                    urlValidateResult = UrlValidateResult.InternalList;
                    return true;
                }

                return false;
            }
            catch (Exception ex)
            {
                Logger.Debug("IsInternalRequest", ex);
                return false;
            }
        }

        public static RequestValidateResult IsValid(string request, out UrlValidateResult urlValidateResult)
        {
            urlValidateResult = UrlValidateResult.Normal;
            if (!IsFilterOn)
            {
                return RequestValidateResult.OK;
            }

            try
            {
                if (request.Length > MaxRequestLength)
                {
                    return RequestValidateResult.Warning;
                }

                //Parse the Http Request Type
                var firstSpaceIndex = request.IndexOf(' ');
                var requestType = request.Substring(0, firstSpaceIndex).ToUpper();

                //Parse the Requested Path
                var sencondSpaceIndex = request.IndexOf(' ', firstSpaceIndex + 1);
                var requestUrl = request.Substring(firstSpaceIndex + 1, sencondSpaceIndex - firstSpaceIndex - 1);

                //Parse the User-Agent
                var sencondeLineIndex = request.IndexOf("\r\n", StringComparison.OrdinalIgnoreCase) + 1;
                var userAgentIndex = request.IndexOf(UserAgentKey, sencondeLineIndex, StringComparison.OrdinalIgnoreCase);
                var endOfUserAgentIndex = request.IndexOf("\r\n", userAgentIndex, StringComparison.OrdinalIgnoreCase);
                var userAgentLength = endOfUserAgentIndex - userAgentIndex;
                var userAgentLine = request.Substring(userAgentIndex, userAgentLength);
                var userAgent = userAgentLine.Split(':')[1].Trim();

                //----------user agent
                if (!userAgent.StartsWith(UserAgentStartKey))
                {
                    return RequestValidateResult.Warning;
                }


                var userAgentDetail = DecodeUserAgent(userAgent);

                //----------version
                if (userAgentDetail.BrowserVersion < MinBrowserVersion)
                {
                    return RequestValidateResult.BrowserOutOfDate;
                }


                //----------url
                urlValidateResult = ValidateUrl(requestUrl, HTTPMethodConnectKey.Equals(requestType, StringComparison.OrdinalIgnoreCase));
                switch (urlValidateResult)
                {
                    case UrlValidateResult.Normal:
                        break;
                    case UrlValidateResult.WhiteList:
                        return RequestValidateResult.OK;
                    case UrlValidateResult.BlackList:
                        return RequestValidateResult.Warning;
                }

                //----------keywords
                switch (requestType)
                {
                    case HTTPMethodGetKey:
                    case HTTPMethodPostKey:
                        if (ValidateKeywords(request))
                        {
                            return RequestValidateResult.ErrorKeywords;
                        }
                        break;
                    case HTTPMethodConnectKey:
                        break;
                    default:
                        return RequestValidateResult.Warning;
                }

                var requestDetail = new RequestDetail
                    {
                        RemoteIP = userAgentDetail.RemoteIP,
                        Url = requestUrl,
                        Type = requestType,
                        RemoteUser = userAgentDetail.RemoteUser,
                        RemoteMachine = userAgentDetail.RemoteMachine
                    };
                Logger.Info(requestDetail.ToString());

                return RequestValidateResult.OK;
            }
            catch (Exception ex)
            {
                Logger.Debug("IsValid", ex);
                return RequestValidateResult.Warning;
            }
        }

        public static RequestValidateResult IsValidKeepAlive(string request)
        {
            if (!IsFilterOn)
            {
                return RequestValidateResult.OK;
            }

            try
            {
                if (request.Length > MaxRequestLength)
                {
                    return RequestValidateResult.Warning;
                }

                if (ValidateKeywords(request))
                {
                    return RequestValidateResult.ErrorKeywords;
                }

                return RequestValidateResult.OK;
            }
            catch (Exception ex)
            {
                Logger.Debug("IsValid", ex);
                return RequestValidateResult.Warning;
            }
        }

        public static string RebuildRequest(string request)
        {
            var sencondeLineIndex = request.IndexOf("\r\n", StringComparison.OrdinalIgnoreCase) + 1;
            var userAgentIndex = request.IndexOf(UserAgentKey, sencondeLineIndex, StringComparison.OrdinalIgnoreCase);
            var endOfUserAgentIndex = request.IndexOf("\r\n", userAgentIndex, StringComparison.OrdinalIgnoreCase);
            var userAgentLength = endOfUserAgentIndex - userAgentIndex;

            //Replace User-Agent with the default one, disguise chrome browser.
            var removeUserAgentRequest = request.Remove(userAgentIndex, userAgentLength);
            var fixedRequest = removeUserAgentRequest.Insert(userAgentIndex, DefaultUserAgent);

            return fixedRequest;
        }

        private static UrlValidateResult ValidateUrl(string url, bool isHttps)
        {
            if (isHttps)
            {
                if (BlackList.Any(item => url.IndexOf(item, StringComparison.OrdinalIgnoreCase) > -1))
                {
                    return UrlValidateResult.BlackList;
                }

                if (WhiteList.Any(item => url.IndexOf(item, StringComparison.OrdinalIgnoreCase) > -1))
                {
                    return UrlValidateResult.WhiteList;
                } 
            }
            else
            {
                var host = new Uri(url).Host;

                if (BlackList.Any(item => host.IndexOf(item, StringComparison.OrdinalIgnoreCase) > -1))
                {
                    return UrlValidateResult.BlackList;
                }

                if (WhiteList.Any(item => host.IndexOf(item, StringComparison.OrdinalIgnoreCase) > -1))
                {
                    return UrlValidateResult.WhiteList;
                } 
            }

            return UrlValidateResult.Normal;
        }

        private static bool ValidateKeywords(string url)
        {
            return Keywords.Any(keyword => url.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0);
        }

        private static UserAgentDetail DecodeUserAgent(string userAgent)
        {
            var dashIndex = userAgent.IndexOf('/');
            var spaceIndex = userAgent.IndexOf(' ');

            var versionStr = userAgent.Substring(dashIndex + 1, spaceIndex - dashIndex - 1);
            var version = Version.Parse(versionStr);


            var bracketLeftIndex = userAgent.IndexOf('(');
            var bracketRightIndex = userAgent.IndexOf(')');
            var content = userAgent.Substring(bracketLeftIndex + 1, bracketRightIndex - bracketLeftIndex - 1);

            var contents = content.Split(';');

            var remoteUser = contents[0].Trim();
            var remoteMachine = contents[1].Trim();
            var remoteIP = contents[2].Trim();

            return new UserAgentDetail { BrowserVersion = version, RemoteUser = remoteUser, RemoteMachine = remoteMachine, RemoteIP = remoteIP };
        }

    }



}
